/*
 * (C) Copyright 2009
 * Vipin Kumar, ST Micoelectronics, vipin.kumar@st.com.
 * Copyright (C) 2022 - 2023, Phytium Technology Co., Ltd. All rights reserved.<BR>
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

#include <Base.h>
#include <Uefi/UefiBaseType.h>
#include <Library/TimerLib.h>
#include <Library/DebugLib.h>
#include <Library/PcdLib.h>
#include <Library/IoLib.h>

#include "I2CLib.h"
#include "I2C.h"

//void *BusAddress = (void*)0x28006000;

/*
 * i2c_enable - Enable I2C
 */
static
void
i2c_enable(
  UINTN BusAddress
  )
{
	unsigned int enbl;

	/* Enable i2c */
	enbl = readl(BusAddress + DW_IC_ENABLE);
	enbl |= IC_ENABLE_0B;
	writel(enbl, BusAddress + DW_IC_ENABLE);
}

/*
 * i2c_disable - Disable I2C
 */
static
void
i2c_disable(
  UINTN BusAddress
  )
{
	unsigned int enbl;

	/* Disable i2c */
	enbl = readl(BusAddress + DW_IC_ENABLE);
	enbl &= ~IC_ENABLE_0B;
	writel(enbl, BusAddress + DW_IC_ENABLE);
}

/*
 * set_speed - Set the i2c speed mode (standard, high, fast)
 * @i2c_spd:	required i2c speed mode
 *
 * Set the i2c speed mode (standard, high, fast)
 */
static
void
set_speed (
  UINTN BusAddress,
  int i2c_spd
  )
{
	unsigned int cntl;
	unsigned int hcnt, lcnt;
	unsigned int enbl;

	/* to set speed cltr must be disabled */
	enbl = readl(BusAddress + DW_IC_ENABLE);
	enbl &= ~IC_ENABLE_0B;
	writel(enbl, BusAddress + DW_IC_ENABLE);

	cntl = (readl(BusAddress + DW_IC_CON) & (~IC_CON_SPD_MSK));

	switch (i2c_spd) {
	case I2C_MAX_SPEED:
		cntl |= IC_CON_SPD_HS;
		hcnt = (IC_CLK * MIN_HS_SCL_HIGHTIME) / NANO_TO_MICRO;
		writel(hcnt, BusAddress + DW_IC_HS_SCL_HCNT);
		lcnt = (IC_CLK * MIN_HS_SCL_LOWTIME) / NANO_TO_MICRO;
		writel(lcnt, BusAddress + DW_IC_HS_SCL_LCNT);
		break;

	case I2C_STANDARD_SPEED:
		cntl |= IC_CON_SPD_SS;
		hcnt = (IC_CLK * MIN_SS_SCL_HIGHTIME) / NANO_TO_MICRO;
		writel(hcnt, BusAddress + DW_IC_SS_SCL_HCNT);
		lcnt = (IC_CLK * MIN_SS_SCL_LOWTIME) / NANO_TO_MICRO;
		writel(lcnt, BusAddress + DW_IC_SS_SCL_LCNT);
		break;

	case I2C_FAST_SPEED:
	default:
		cntl |= IC_CON_SPD_FS;
		hcnt = (IC_CLK * MIN_FS_SCL_HIGHTIME) / NANO_TO_MICRO;
		writel(hcnt, BusAddress + DW_IC_FS_SCL_HCNT);
		lcnt = (IC_CLK * MIN_FS_SCL_LOWTIME) / NANO_TO_MICRO;
		writel(lcnt, BusAddress + DW_IC_FS_SCL_LCNT);
		break;
	}

	writel(cntl, BusAddress + DW_IC_CON);

	/* Enable back i2c now speed set */
	enbl |= IC_ENABLE_0B;
	writel(enbl, BusAddress + DW_IC_ENABLE);
}

/*
 * i2c_set_bus_speed - Set the i2c speed
 * @speed:	required i2c speed
 *
 * Set the i2c speed.
 */
int
i2c_set_bus_speed(
  UINTN BusAddress,
  int speed
  )
{
	if (speed >= I2C_MAX_SPEED)
		set_speed(BusAddress,I2C_MAX_SPEED);
	else if (speed >= I2C_FAST_SPEED)
		set_speed(BusAddress,I2C_FAST_SPEED);
	else
		set_speed(BusAddress,I2C_STANDARD_SPEED);

	return 0;
}

/*
 * i2c_get_bus_speed - Gets the i2c speed
 *
 * Gets the i2c speed.
 */
int
i2c_get_bus_speed (
  UINTN BusAddress
  )
{
	int cntl;

	cntl = (readl(BusAddress + DW_IC_CON) & IC_CON_SPD_MSK);

	if (cntl == IC_CON_SPD_HS)
		return I2C_MAX_SPEED;
	else if (cntl == IC_CON_SPD_FS)
		return I2C_FAST_SPEED;
	else if (cntl == IC_CON_SPD_SS)
		return I2C_STANDARD_SPEED;

	return 0;
}

/*
 * i2c_setaddress - Sets the target slave address
 * @i2c_addr:	target i2c address
 *
 * Sets the target slave address.
 */
static
void
i2c_setaddress (
  UINTN BusAddress,
  unsigned int i2c_addr
  )
{

  writel(i2c_addr, BusAddress + DW_IC_TAR);
}

/*
 * i2c_flush_rxfifo - Flushes the i2c RX FIFO
 *
 * Flushes the i2c RX FIFO
 */
static
void
i2c_flush_rxfifo (
  UINTN BusAddress
  )
{
  while (readl(BusAddress + DW_IC_STATUS) & IC_STATUS_RFNE)
    readl(BusAddress + DW_IC_DATA_CMD);
}

/*
 * i2c_wait_for_bb - Waits for bus busy
 *
 * Waits for bus busy
 */
static
int
i2c_wait_for_bb (
  UINTN BusAddress
  )
{
	  int  timeout = I2C_BYTE_TO_BB;
	  
	  while ((readl(BusAddress + DW_IC_STATUS) & IC_STATUS_MA) ||
			 !(readl(BusAddress + DW_IC_STATUS) & IC_STATUS_TFE)) {
	  
		  /* Evaluate timeout */
		  MicroSecondDelay(1000);
		  if (timeout-- == 0) {
			  /*DEBUG ((EFI_D_ERROR, "Timed out. i2c i2c_wait_for_bb Failed\n"));*/
			  return 1;
		  }
	  }
	  
	  return 0;

}

/* check parameters for i2c_read and i2c_write */
static
int
check_params (
  UINTN BusAddress,
  unsigned int addr,
  int alen,
  unsigned char *buffer,
  int len
  )
{

	if (buffer == NULL) {
		//DEBUG ((EFI_D_ERROR, "Buffer is invalid\n"));
		return 1;
	}

	if (alen > 1) {
		//DEBUG ((EFI_D_ERROR, "addr len %d not supported\n", alen));
		return 1;
	}

	if (addr + len > 256) {
		//DEBUG ((EFI_D_ERROR, "address out of range\n"));
		return 1;
	}

	return 0;
}

static
int
i2c_xfer_init (
  UINTN BusAddress,
  unsigned char chip,
  unsigned int addr,
  unsigned int alen
  )
{
  UINT8   i;
  if (i2c_wait_for_bb(BusAddress))
    return 1;

  i2c_disable(BusAddress);

  i2c_setaddress(BusAddress,chip);

  i2c_enable(BusAddress);
  /// REG addr
  for (i = alen;i > 0;i--){
    //DEBUG((EFI_D_INFO,"the number i is %d \n",i));
    addr = (addr >> (i-1)*8) & 0xFF;
    writel(addr, BusAddress + DW_IC_DATA_CMD);
  }
  ///

  return 0;
}

static
int
i2c_xfer_finish (
  UINTN BusAddress
  )
{
	UINTN  timeout;
	//DEBUG((EFI_D_INFO, " BusAddress is %x line %d:\n",BusAddress,__LINE__));

	timeout = I2C_STOPDET_TO;
	while (1) {
		if ((readl(BusAddress + DW_IC_RAW_INTR_STAT) & IC_STOP_DET)) {
			readl(BusAddress + DW_IC_CLR_STOP_DET);
			break;
		} else {
			MicroSecondDelay(1000);
			if (timeout-- == 0) {
				//DEBUG ((EFI_D_ERROR, "Timed out. i2c i2c_xfer_finish Failed\n"));
				break;
			}
		}
	}

    if (i2c_wait_for_bb(BusAddress)) {
        DEBUG ((EFI_D_ERROR, "Timed out waiting for bus\n"));
        DEBUG((EFI_D_INFO,"ABRT:%x\n",readl(BusAddress + DW_IC_TX_ABRT_SOURCE)));
        return 1;
    }

    i2c_flush_rxfifo(BusAddress);

    /* Wait for read/write operation to complete on actual memory */
    MicroSecondDelay(10);

	return 0;
}


/*
 * i2c_read - Read from i2c memory
 * @chip:	target i2c address
 * @addr:	address to read from
 * @alen:
 * @buffer:	buffer for read data
 * @len:	no of bytes to be read
 *
 * Read from i2c memory.
 */
int
i2c_read (
  UINTN          BusAddress,
  UINT32         chip,
  UINT16         addr,
  int            alen,
  unsigned char *buffer,
  int            len
  )
{
	int timeout;
	//DEBUG((EFI_D_INFO, " BusAddress is 0x%x Slaveaddress is 0x%x Reg is 0x%x %a line %d:\n",BusAddress,chip,addr,__FUNCTION__,__LINE__));

	//if (check_params(addr, alen, buffer, len))
	//return 1;

	if (i2c_xfer_init(BusAddress,chip, addr ,alen))
		return 1;


	timeout = I2C_BYTE_TO;
	while (len) {
		if (len == 1)
			writel(IC_CMD | IC_STOP, BusAddress + DW_IC_DATA_CMD);
		else
			writel(IC_CMD, BusAddress + DW_IC_DATA_CMD);
#if 1
		if (readl(BusAddress + DW_IC_STATUS) & IC_STATUS_RFNE) {
			MicroSecondDelay(1600);
		    //DEBUG ((EFI_D_ERROR, "FIFO HAVE DATA 1 -------------\n"));
			*buffer  = (unsigned char)readl(BusAddress + DW_IC_DATA_CMD);
			len--;
			buffer++;
			timeout = I2C_BYTE_TO;
		} else {
			MicroSecondDelay(1000);
			//DEBUG ((EFI_D_ERROR, "need time delay-------------\n"));
			if (timeout-- == 0) {
                // Don't use DEBUG in runtime.
				//DEBUG ((EFI_D_ERROR, "Timed out. i2c read Failed\n"));
				return 1;
			}
           if (readl(BusAddress + DW_IC_STATUS) & IC_STATUS_RFNE) {
                //DEBUG ((EFI_D_ERROR, "FIFO HAVE DATA 2  -------------\n"));
                *buffer = (unsigned char)readl(BusAddress + DW_IC_DATA_CMD);
	             len--;
	             buffer ++;
	             timeout = I2C_BYTE_TO;
           }
		}
#endif
#if 0
       while (!(readl(BusAddress + DW_IC_STATUS) & IC_STATUS_RFNE)){
         DEBUG((DEBUG_ERROR, " %a %a line %d:\n",__FILE__,__func__,__LINE__));
         timeout++;
        // if(timeout >= 5000)
        //    break;
       }
     // if(timeout >= 5000){
      //  DEBUG((EFI_D_INFO,"read error!\n"));
      //    return EFI_TIMEOUT;
     // }
      *buffer= (unsigned char)readl(BusAddress + DW_IC_DATA_CMD);
       len--;
       buffer++;
#endif
    }
  return i2c_xfer_finish(BusAddress);
}

void
spd_setpage(
  UINTN BusAddress,
  unsigned char chip,
  int page_num
  )
{
	i2c_enable(BusAddress);
	if(page_num == 0x1)
		writel(0x37, BusAddress + DW_IC_TAR );
	else
		writel(0x36, BusAddress + DW_IC_TAR );

	writel(0x00, BusAddress + DW_IC_DATA_CMD);
	writel(0x0|IC_STOP, BusAddress + DW_IC_DATA_CMD);

	/*******************For NoAck Response************************/

	while(readl(BusAddress + DW_IC_STATUS) != 0x6);

	if(readl(BusAddress+DW_IC_INTR_STAT) & 0x40){
		volatile unsigned int i = 0;
		for(i = 0; i < 100; i++);
		readl(BusAddress + DW_IC_INTR_STAT);
		readl(BusAddress + DW_IC_STATUS);
		readl(BusAddress + DW_IC_CLR_TX_ABRT);
		readl(BusAddress + DW_IC_INTR_STAT);
		readl(BusAddress + DW_IC_STATUS);
	}

}


/*
 * i2c_write - Write to i2c memory
 * @chip:	target i2c address
 * @addr:	address to read from
 * @alen:
 * @buffer:	buffer for read data
 * @len:	no of bytes to be read
 *
 * Write to i2c memory.
 */
int
i2c_write (
  UINTN BusAddress,
  unsigned char chip,
  UINT64 addr,
  int alen,
  unsigned char *buffer,
  int len
  )
{
	  int nb = len, timeout;
	  int cnt = 0;
	  
	  if (check_params(BusAddress, addr, alen, buffer, len))
		  return 1;
	  
	  if (i2c_xfer_init(BusAddress, chip, addr,alen))
		  return 1;
	  
	  timeout = nb * I2C_BYTE_TO;
	  while (len) {
		  if (readl(BusAddress + DW_IC_STATUS) & IC_STATUS_TFNF) {
			  if(len > 1){	//not last one
				  if(((cnt+1) % 8 == 0)){
					  //DEBUG((EFI_D_ERROR, " send stop ,num is %d.len is %d \n",cnt,len));
					  writel(*buffer | IC_STOP, BusAddress + DW_IC_DATA_CMD);  //send stop
					  MicroSecondDelay(100000);
				  }else if(cnt !=0 && cnt %8 == 0){
					  DEBUG((EFI_D_ERROR, "re send start ,num is %d.\n",cnt));
					  i2c_xfer_init(BusAddress,chip, addr+cnt,alen);
					  if (readl(BusAddress + DW_IC_STATUS) & IC_STATUS_TFNF) {
						  writel(*buffer, BusAddress + DW_IC_DATA_CMD);
					  }
	  
				  }else{
					  writel(*buffer, BusAddress + DW_IC_DATA_CMD);
				  }
	  
			  }else{  //last one
				  if(cnt !=0 && cnt %8 == 0){
                      // Don't use DEBUG in runtime.
					  //DEBUG((EFI_D_ERROR, " last one: restart and stop ,num is %d.len is %d \n",cnt,len));
					  i2c_xfer_init(BusAddress,chip, addr+cnt,alen);
					  if (readl(BusAddress + DW_IC_STATUS) & IC_STATUS_TFNF) {
						  writel(*buffer| IC_STOP, BusAddress + DW_IC_DATA_CMD);
					  }
				  }
				  else{
					  //DEBUG((EFI_D_ERROR, " last one :send stop ,num is %d.len is %d \n",cnt,len));
					  writel(*buffer| IC_STOP, BusAddress + DW_IC_DATA_CMD);
				  }
			  }
			  buffer++;
			  cnt++;
			  len--;
			  timeout = nb * I2C_BYTE_TO;
		  } else {
			  MicroSecondDelay(1000);
			  if (timeout-- == 0) {
				  //DEBUG ((EFI_D_ERROR, "Timed out. i2c write Failed\n"));
				  return 1;
			  }
		  }
	  }
	  
	  return i2c_xfer_finish(BusAddress);

}

/*
 * i2c_init - Init function
 * @speed:	required i2c speed
 * @slaveadd:	slave address for the device
 *
 * Initialization function.
 */
void
i2c_init (
  UINTN BusAddress,
  unsigned int Speed,
  int SlaveAddress
  )
{

  /* Disable i2c */
	i2c_disable(BusAddress);

	writel((IC_CON_SD | IC_CON_RE | IC_CON_MM), BusAddress + DW_IC_CON);

	writel(IC_RX_TL, BusAddress + DW_IC_RX_TL);
	writel(IC_TX_TL, BusAddress + DW_IC_TX_TL);

	i2c_set_bus_speed(BusAddress,Speed);

	writel(IC_STOP_DET, BusAddress + DW_IC_INTR_MASK);
	writel(SlaveAddress, BusAddress + DW_IC_SAR);

    i2c_enable(BusAddress);
}

int
enable_i2c_as_slaver (
  UINTN BusAddress,
  UINT8 slave_address
  )
{

	i2c_disable(BusAddress);
	//set slave address
	writel(slave_address, BusAddress + DW_IC_SAR);
	//config slave mode
	writel(0, BusAddress + DW_IC_CON);
	//set speed
	set_speed(BusAddress,I2C_FAST_SPEED);
	//mask set
	/*writel(IC_RD_REQ | IC_RX_FULL, BusAddress + DW_IC_INTR_MASK);*/
	//Rxfifo set
	/*writel(4, BusAddress + DW_IC_TX_TL);*/

	i2c_enable(BusAddress);
    return 0;
}

